package com.partsoft.dits.integrate;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import com.partsoft.dits.AbstractConfigurableDataDescriptorManager;
import com.partsoft.dits.ConfigurableBean;
import com.partsoft.dits.DescriptorAccepter;
import com.partsoft.dits.DescriptorDepends;
import com.partsoft.dits.EdgeDescriptor;
import com.partsoft.dits.LdapDataEdgeDescriptor;
import com.partsoft.dits.dsm.DataSourceDescriptor;
import com.partsoft.dits.ldap.LdapContextDescriptor;

@SuppressWarnings("unchecked")
public abstract class ConfigurableLdapDataDescriptorManager<E extends LdapDataEdgeDescriptor> extends
		AbstractConfigurableDataDescriptorManager<E> implements ConfigurableBean, LdapDataDescriptorRegistry<E>,
		DescriptorDepends<E> {

	private String descriptorIdPrefix;

	private Set<String> contextIds = Collections.synchronizedSet(new HashSet<String>(2));

	private Map<String, Set<E>> ldapDataDescriptors = Collections.synchronizedMap(new HashMap<String, Set<E>>(5));

	private Set<Class<? extends EdgeDescriptor>> dependsClasses;
	{
		dependsClasses = new HashSet<Class<? extends EdgeDescriptor>>(1);
		dependsClasses.add(LdapContextDescriptor.class);
	}

	public ConfigurableLdapDataDescriptorManager() {
	}

	public ConfigurableLdapDataDescriptorManager(String contextConfigLocation, String transformScriptDirectoryLocation,
			String descriptorIdPrefix) {
		super(contextConfigLocation, transformScriptDirectoryLocation);
		setDescriptorIdPrefix(descriptorIdPrefix);
	}

	public ConfigurableLdapDataDescriptorManager(String contextConfigLocation, String transformScriptDirectoryLocation,
			String descriptorIdPrefix, boolean createNoExists) {
		super(contextConfigLocation, transformScriptDirectoryLocation, createNoExists);
		setDescriptorIdPrefix(descriptorIdPrefix);
	}

	/**
	 * 描述符ID前缀
	 * 
	 * @param descriptorIdPrefix
	 */
	public void setDescriptorIdPrefix(String descriptorIdPrefix) {
		this.descriptorIdPrefix = descriptorIdPrefix;
	}

	protected String getDescriptorIdPrefix() {
		return descriptorIdPrefix;
	}

	public E getDescriptor(String contextId, String parentDN, String propertyId, String messageCode) {
		assertAlreadyInit();
		String publishBeanId = resolveDescriptorIdentifier(contextId, parentDN, propertyId, messageCode);
		return hasDescriptor(publishBeanId) ? getDescriptor(publishBeanId) : null;
	}

	protected String resolveDescriptorIdentifier(String contextId, String parentDN, String propertyId,
			String messageCode) {
		Assert.hasText(contextId);
		Assert.hasText(propertyId);
		Assert.hasText(messageCode);
		StringBuffer idBuffer = new StringBuffer();
		if (StringUtils.hasText(getDescriptorIdPrefix()))
			idBuffer.append(getDescriptorIdPrefix()).append(".");
		idBuffer.append(contextId).append(".");
		if (StringUtils.hasText(parentDN))
			idBuffer.append(parentDN).append(".");
		idBuffer.append(propertyId).append(".").append(messageCode);
		return idBuffer.toString();
	}

	protected String resolveDescriptorIdentifier(E desc) {
		Assert.notNull(desc);
		return StringUtils.hasText(desc.getId()) ? desc.getId() : resolveDescriptorIdentifier(desc.getLdapContextId(),
				desc.getEntityParentDN(), desc.getEntityPropertyId(), desc.getMessageCode());
	}

	@Override
	protected void validateDescriptor(E descriptor) {
		Assert.notNull(descriptor);
		Assert.hasText(descriptor.getLdapContextId(), "必须设置数据源名称");
		Assert.hasText(descriptor.getEntityParentDN(), "必须设置模式名称");
		Assert.hasText(descriptor.getEntityPropertyId(), "必须设置数据表名称");
		Assert.hasText(descriptor.getMessageCode(), "必须设置集成消息名称");
	}

	public E[] getDescriptors(String contextId, String parentDN, String propertyId) {
		String tfid = resolveTableFullId(contextId, parentDN, propertyId);
		E[] returnArray = (E[]) Array.newInstance(getSupportClass(), 0);
		if (ldapDataDescriptors.containsKey(tfid)) {
			List<E> returnList = new LinkedList<E>();
			for (E desc : ldapDataDescriptors.get(tfid)) {
				returnList.add((E) desc.clone());
			}
			returnArray = returnList.toArray(returnArray);
		}
		return returnArray;
	}

	public E[] getDescriptors(String contextId, String parentId, String propertyId, DescriptorAccepter<E> accepter) {
		if (accepter == null)
			return getDescriptors(contextId, parentId, propertyId);
		String tfid = resolveTableFullId(contextId, parentId, propertyId);
		E[] returnArray = (E[]) Array.newInstance(getSupportClass(), 0);
		if (ldapDataDescriptors.containsKey(tfid)) {
			List<E> returnList = new LinkedList<E>();
			for (E desc : ldapDataDescriptors.get(tfid)) {
				if (accepter.accept(desc))
					returnList.add((E) desc.clone());
			}
			returnArray = returnList.toArray(returnArray);
		}
		return returnArray;
	}

	public int countDescriptor(String contextId, String parentDN, String propertyId) {
		int result = 0;
		for (E desc : getDescriptorMap().values()) {
			if (ObjectUtils.nullSafeEquals(contextId, desc.getLdapContextId())
					&& ObjectUtils.nullSafeEquals(parentDN, desc.getEntityParentDN())
					&& ObjectUtils.nullSafeEquals(propertyId, desc.getEntityPropertyId())) {
				result++;
			}
		}
		return result;
	}

	public int countDescriptor(String dsName, String schemaName, String tableName, DescriptorAccepter<E> accepter) {
		if (accepter == null)
			return countDescriptor(dsName, schemaName, tableName);
		int result = 0;
		for (E desc : getDescriptorMap().values()) {
			if (ObjectUtils.nullSafeEquals(dsName, desc.getLdapContextId())
					&& ObjectUtils.nullSafeEquals(schemaName, desc.getEntityParentDN())
					&& ObjectUtils.nullSafeEquals(tableName, desc.getEntityPropertyId()) && accepter.accept(desc)) {
				result++;
			}
		}
		return result;

	}

	/**
	 * 获得脚本内容(从文件中获取);
	 * 
	 * @param e
	 * @return
	 * @throws IOException
	 *             如果不成功则抛出
	 */
	protected String loadTransformScriptContent(E e) throws IOException {
		return FileUtils.readFileToString(new File(getTransformScriptDirectory(),
				resolveDescriptorScriptRelativeFileName(e)), "utf-8");
	}

	/**
	 * 保存脚本内容至文件
	 * 
	 * @param e
	 * @throws IOException
	 */
	protected void saveTransformScriptContext(E e) throws IOException {
		FileUtils.writeStringToFile(
				new File(getTransformScriptDirectory(), resolveDescriptorScriptRelativeFileName(e)),
				e.getTransformScript(), "utf-8");
	}

	@Override
	public Set<Class<? extends EdgeDescriptor>> getDependClasses() {
		return dependsClasses;
	}

	@Override
	public boolean dependDescriptor(EdgeDescriptor descriptor) {
		return getDependClasses().contains(ClassUtils.getUserClass(descriptor)) ? contextIds
				.contains(((DataSourceDescriptor) descriptor).getName()) : false;
	}

	@Override
	public E[] getDescriptorByDepend(EdgeDescriptor descriptor) {
		E[] returnArray = (E[]) Array.newInstance(getSupportClass(), 0);
		if (dependDescriptor(descriptor)) {
			List<E> returnList = new LinkedList<E>();
			for (E e : getDescriptorMap().values()) {
				if (ObjectUtils.nullSafeEquals(e.getLdapContextId(), ((DataSourceDescriptor) descriptor).getName()))
					returnList.add(e);
			}
			returnArray = returnList.toArray(returnArray);
		}
		return returnArray;
	}

	private String resolveTableFullId(String dsName, String schemaName, String tableName) {
		StringBuffer buffer = new StringBuffer();
		buffer.append(dsName).append(".");
		if (StringUtils.hasText(schemaName))
			buffer.append(schemaName).append(".");
		buffer.append(tableName);
		return buffer.toString();
	}

	private void appendProperties(E descriptor) {
		contextIds.add(descriptor.getLdapContextId());
		String tfid = resolveTableFullId(descriptor.getLdapContextId(), descriptor.getEntityParentDN(),
				descriptor.getEntityPropertyId());
		Set<E> list = null;
		if (ldapDataDescriptors.containsKey(tfid))
			list = ldapDataDescriptors.get(tfid);
		else {
			list = new HashSet<E>(5);
			ldapDataDescriptors.put(tfid, list);
		}
		list.add(descriptor);
	}

	private void releaseProperties(E descriptor) {
		contextIds.remove(descriptor.getLdapContextId());
		String tfid = resolveTableFullId(descriptor.getLdapContextId(), descriptor.getEntityParentDN(),
				descriptor.getEntityPropertyId());
		Set<E> list = ldapDataDescriptors.get(tfid);
		if (list != null) {
			list.remove(descriptor);
			if (list.size() == 0)
				ldapDataDescriptors.remove(tfid);
		}
	}

	protected void postAddDescriptor(E descriptor) {
		appendProperties(descriptor);
	};

	protected void postLoadConfiguredDescriptor(E descriptor) {
		appendProperties(descriptor);
	};

	protected void postRemoveDescriptor(E descriptor) {
		releaseProperties(descriptor);
	};

	protected void postUnLoadConfiguredDescriptor(E descriptor) {
		releaseProperties(descriptor);
	};

}
